package com.coder.oj.system.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.coder.oj.common.bo.LangeCountBo;
import com.coder.oj.common.core.BaseQuery;
import com.coder.oj.common.core.JsonPage;
import com.coder.oj.common.entity.Case;
import com.coder.oj.common.entity.Commit;
import com.coder.oj.common.entity.Problem;
import com.coder.oj.common.enums.CommitStatusEnum;
import com.coder.oj.common.enums.LanguageEnum;
import com.coder.oj.common.exception.DataException;
import com.coder.oj.common.mapper.CaseMapper;
import com.coder.oj.common.mapper.CommitMapper;
import com.coder.oj.common.mapper.ProblemMapper;
import com.coder.oj.common.utils.HttpUtils;
import com.coder.oj.system.dto.CommitDto;
import com.coder.oj.system.mapstruct.CommitMapstruct;
import com.coder.oj.system.query.CommitQuery;
import com.coder.oj.system.vo.CommitStatisticsVo;
import com.coder.oj.system.vo.CommitVo;
import com.coder.oj.system.vo.PowerVo;
import com.coder.oj.system.bo.RunCodeVo;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
public class CommitService {

    @Autowired
    private ProblemMapper problemMapper;

    @Autowired
    private CaseMapper caseMapper;

    @Autowired
    private CommitMapper commitMapper;

    @Autowired
    private CommitMapstruct commitMapstruct;

    @Value("${run-code-api}")
    private String runCodeApi;

    /**
     * 提交代码
     * @author Bosen 2023/1/10 13:40
     **/
    public CommitVo runCode(CommitDto params) {
        JSONObject jsonParam = getJsonParam(params);
        // 测试样例
        List<Case> zaseList = new LambdaQueryChainWrapper<>(caseMapper)
                .eq(Case::getProblemId, params.getProblemId()).list();
        // 提交结果
        Commit commit = new Commit();
        commit.setProblemId(params.getProblemId());
        commit.setLanguage(params.getLanguage());
        commit.setCode(params.getCode());
        commit.setStatus(CommitStatusEnum.AC.getValue());
        for(Case zase : zaseList) {
            // 设置输入
            jsonParam.put("input", zase.getInput());
            // 执行响应
            JSONObject jsonResult = HttpUtils.httpPost(runCodeApi, jsonParam);
            RunCodeVo res = JSON.parseObject(JSON.toJSONString(jsonResult), new TypeReference<RunCodeVo>() {});
            // 代码未正常运行
            if(res.getCode() != 0) {
                if(res.getCode() == 1) {
                    commit.setStatus(CommitStatusEnum.TLE.getValue());
                    commit.setError("代码运行超过时间限制");
                    break;
                }
                if(res.getCode() == 2) {
                    throw new DataException("暂时不支持该语言");
                }
                if(res.getCode() == 3) {
                    throw new DataException("提交的代码太长，最多允许400KB");
                }
                if(res.getCode() == 4) {
                    throw new DataException("输入的太长，最多允许20KB");
                }
                if(res.getCode() == 5) {
                    throw new DataException("确认选择的语言是否正确，或代码出错");
                }
                commit.setStatus(CommitStatusEnum.CE.getValue());
                commit.setError(subErrorMsg(res.getMsg()));
                break;
            }
            // 代码运行时发生异常
            if(StringUtils.isNotBlank(res.getData().getStderr())) {
                if(res.getData().getStderr().contains("Killed")
                        || res.getData().getStderr().contains("java.lang.StackOverflowError")
                        || res.getData().getStderr().contains("java.lang.OutOfMemoryError")) {
                    // 超过代码运行内存限制
                    commit.setStatus(CommitStatusEnum.MLE.getValue());
                } else if("TLE".equals(res.getMsg())) {
                    // 超过代码运行时间限制
                    commit.setStatus(CommitStatusEnum.TLE.getValue());
                } else {
                    // 代码运行时异常
                    commit.setStatus(CommitStatusEnum.CE.getValue());
                }
                commit.setError(subErrorMsg(res.getData().getStderr()));
                break;
            }
            // 代码已成功运行，答案不正确
            if(!checkout(res.getData().getStdout(), zase.getOutput())) {
                commit.setStatus(CommitStatusEnum.WA.getValue());
                commit.setError(subErrorMsg("最后执行的测试样例：\n\n输入：" + zase.getInput() + "\n\n预期：" + zase.getOutput() + "\n\n输出：" + res.getData().getStdout()));
                break;
            }
        }
        commitMapper.insert(commit);
        return commitMapstruct.toVO(commit);
    }

    /**
     * 代码调试
     * @author Bosen 2023/2/13 15:19
     **/
    public String debugCode(CommitDto params) {
        StringBuilder result = new StringBuilder();
        JSONObject jsonParam = getJsonParam(params);
        for(String input : params.getDebugCase().split("\n")) {
            // 设置输入
            jsonParam.put("input", input);
            // 执行响应
            JSONObject jsonResult = HttpUtils.httpPost(runCodeApi, jsonParam);
            RunCodeVo res = JSON.parseObject(JSON.toJSONString(jsonResult), new TypeReference<RunCodeVo>() {});
            result.append(res.getData().getStdout());
            if(StringUtils.isNotBlank(res.getData().getStderr())) {
                result.append(res.getData().getStderr());
                return result.toString();
            }
        }
        return result.toString();
    }

    /**
     * 输出校验
     * @author Bosen 2023/1/12 15:26
     **/
    public boolean checkout(String output, String answer) {
        return answer.equals(trimFirstAndLastChar(output, "\n"));
    }

    /**
     * 去除前后字符 
     * @author Bosen 2023/1/12 15:25
     **/
    public String trimFirstAndLastChar(String str, String element){
        boolean beginIndexFlag = true;
        boolean endIndexFlag = true;
        do{
            int beginIndex = str.indexOf(element) == 0 ? 1 : 0;
            int endIndex = str.lastIndexOf(element) + 1 == str.length() ? str.lastIndexOf(element) : str.length();
            str = str.substring(beginIndex, endIndex);
            beginIndexFlag = (str.indexOf(element) == 0);
            endIndexFlag = (str.lastIndexOf(element) + 1 == str.length());
        } while (beginIndexFlag || endIndexFlag);
        return str;
    }

    /**
     * 分页查询
     * @author Bosen 2023/1/10 13:42
     **/
    public JsonPage<CommitVo> getCommitPage(CommitQuery params) {
        LambdaQueryWrapper<Commit> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper
                .eq(ObjectUtils.isNotEmpty(params.getProblemId()), Commit::getProblemId, params.getProblemId())
                .eq(Commit::getCreateBy, params.getUserId())
                .orderByDesc(Commit::getId);
        IPage<Commit> page = commitMapper.selectPage(new Page<>(params.getPage(), params.getPageSize()), queryWrapper);
        List<CommitVo> collect = page.getRecords().stream().map(commitMapstruct::toVO).collect(Collectors.toList());
        return new JsonPage<>(page.getCurrent(), page.getSize(), page.getTotal(), collect);
    }

    /**
     * 提交统计
     * @author Bosen 2023/1/10 13:40
     **/
    public CommitStatisticsVo statistics(long problemId) {
        CommitStatisticsVo vo = new CommitStatisticsVo();
        vo.setAc(new LambdaQueryChainWrapper<>(commitMapper)
                .eq(Commit::getProblemId, problemId)
                .eq(Commit::getStatus, CommitStatusEnum.AC)
                .count());
        vo.setTotal(new LambdaQueryChainWrapper<>(commitMapper)
                .eq(Commit::getProblemId, problemId)
                .count());
        return vo;
    }

    /**
     * 能力值 
     * @author Bosen 2023/1/30 11:17
     **/
    public List<PowerVo> getPowerList(Long userId) {
        List<LangeCountBo> totalList = commitMapper.getUserTotalCommit(userId);
        List<LangeCountBo> acList = commitMapper.getUserAcCommit(userId);
        Map<Integer, Integer> acMap = acList.stream().collect(Collectors.toMap(LangeCountBo::getLange, LangeCountBo::getCount, (key, value) -> value));

        return totalList.stream().map(total -> {
            PowerVo vo = new PowerVo();
            vo.setTotal(total.getCount());
            vo.setAc(acMap.getOrDefault(total.getLange(), 0));
            if(total.getCount() != 0) {
                vo.setAcPercent(((double) vo.getAc() / vo.getTotal()) * 100);
            }
            vo.setLange(LanguageEnum.getEnum(total.getLange()).getName());
            return vo;
        }).collect(Collectors.toList());
    }

    /**
     * 用户提交状态（true已通过，false尝试未通过，null未尝试）
     * @author Bosen 2023/1/30 15:50
     **/
    public Boolean getUserCommitStatusByProblem(Long userId, Long problemId) {
        int total = new LambdaQueryChainWrapper<>(commitMapper)
                .eq(Commit::getProblemId, problemId)
                .eq(Commit::getCreateBy, userId).count();
        if(total == 0) {
            return null;
        }
        int ac = new LambdaQueryChainWrapper<>(commitMapper)
                .eq(Commit::getStatus, CommitStatusEnum.AC.getValue())
                .eq(Commit::getProblemId, problemId)
                .eq(Commit::getCreateBy, userId).count();
        return ac != 0;
    }

    /**
     * 截取异常信息
     * @author Bosen 2023/1/31 14:48
     **/
    private String subErrorMsg(String msg) {
        if(msg.length() > 1000) {
            return msg.substring(0, 1000);
        }
        return msg;
    }

    /**
     * 设置参数
     * @author Bosen 2023/2/13 15:21
     **/
    private JSONObject getJsonParam(CommitDto params) {
        // 题目信息
        Problem problem = problemMapper.selectById(params.getProblemId());
        if(ObjectUtils.isEmpty(problem)) {
            throw new DataException("题目不存在");
        }
        // 设置参数
        JSONObject jsonParam = new JSONObject();
        jsonParam.put("lang", LanguageEnum.getEnum(params.getLanguage()).getName());
        jsonParam.put("timeout", String.valueOf(problem.getTimeLimit()));
        jsonParam.put("memory", problem.getMemoLimit() + "MB");
        jsonParam.put("code", params.getCode());
        return jsonParam;
    }
}
